import abc
from copy import deepcopy
from typing import Dict, Set
from lxml.html import fromstring, tostring
from lxml.html.clean import Cleaner
from lxml.html.defs import safe_attrs
from core.exception import AlertException
from .allowed_dict import *


class XSSCleaner(Cleaner):
    """
lxml.html.clean.继承Cleaner类
即使要过滤的源数据不是HTML tree。
允许在过滤后保留原始格式。

strip_outer_div_tag
-删除顶级父标签<div></div>

clean_html
-删除不允许的标记时，源内容不是一个HTML元素。
覆盖Cleaner.clean_html方法以保留原始格式的方法。
    """

    def strip_outer_div_tag(self, html_string):
        html_string = html_string[5:-6]
        return html_string

    def clean_html(self, html: str):
        html = f'<div>{html}</div>'
        html_tree = fromstring(html)
        self(html_tree)
        cleaned_html = tostring(html_tree, encoding='unicode', with_tail=False)
        return self.strip_outer_div_tag(cleaned_html)


class BaseSanitizer(metaclass=abc.ABCMeta):
    """
    SubjectSanitizer, ContentSanitizer 类的抽象类.
    我们定义了一个通用公差标签和一个通用公差属性.

    __init___
      - lxml.html.clean.Cleaner 类和常见的允许标记, 初始化公共公差属性.
      - is_with_library_attrs通过 lxml 由图书馆提供
        html 它决定是否允许该物业. 默认值为 False就这样..

    get_combined_filter_dict
      - 将常见的允许标签和属性与单独的允许标签和属性组合.

    get_cleaned_data
       - 在继承的类中使用重写.
         最后允许 HTML 仅返回标记和属性.
    """

    def __init__(self, is_with_library_attrs=False):
        self.cleaner = XSSCleaner(safe_attrs_only=True, remove_unknown_tags=False)
        self.base_allowed_tags_dict = deepcopy(common_allowed_tags_dict)
        self.base_allowed_attrs_dict = deepcopy(common_allowed_attrs_dict)
        if is_with_library_attrs:
            self.base_allowed_attrs_dict['library'] = safe_attrs

    def get_combined_filter_dict(
        self,
        base_allowed_dict: Dict[str, Set[str]],
        private_allowed_dict: Dict[str, Set[str]],
    ) -> Dict[str, Set[str]]:
        for key, tags in private_allowed_dict.items():
            base_allowed_dict[key] = base_allowed_dict.get(key, set()).union(tags)
        return base_allowed_dict

    @abc.abstractmethod
    def get_cleaned_data(self, html_content: str):
        pass


class SubjectSanitizer(BaseSanitizer):
    """
    公告板等。 '标题'插入到 HTML的 XSS 防止攻击 Sanitizer 类
    """

    def __init__(self, is_with_library_attrs: bool = False):
        super().__init__(is_with_library_attrs)
        
        combined_tags = self.get_combined_filter_dict(self.base_allowed_tags_dict, subject_private_allowed_tags_dict)
        combined_attrs = self.get_combined_filter_dict(self.base_allowed_attrs_dict, subject_private_allowed_attrs_dict)
        self.cleaner.allow_tags = {tag for tags in combined_tags.values() for tag in tags}
        self.cleaner.safe_attrs = {attr for attrs in combined_attrs.values() for attr in attrs}
    
    def get_cleaned_data(self, html_content: str) -> str:
        cleaned_html = self.cleaner.clean_html(html_content)

        if not cleaned_html:
            raise AlertException("不允许 HTML 更改标签后，请重写标题.", 400)

        return cleaned_html


class ContentSanitizer(BaseSanitizer):
    """
    公告板等。 '文本'插入到 HTML的 XSS 防止攻击 Sanitizer 类
    """

    def __init__(self, is_with_library_attrs: bool = False):
        super().__init__(is_with_library_attrs)
        combined_tags = self.get_combined_filter_dict(self.base_allowed_tags_dict, content_private_allowed_tags_dict)
        combined_attrs = self.get_combined_filter_dict(self.base_allowed_attrs_dict, content_private_allowed_attrs_dict)
        self.cleaner.allow_tags = {tag for tags in combined_tags.values() for tag in tags}
        self.cleaner.safe_attrs = {attr for attrs in combined_attrs.values() for attr in attrs}

    def get_cleaned_data(self, html_content: str) -> str:
        cleaned_html = self.cleaner.clean_html(html_content)
        return cleaned_html